Repository navigation
perf(heap): look up Gen1/Gen2 membership without the chunk lock - #5409
harivansh-afk wants to merge 1 commit into
Conversation
generation_of runs on every pointer store, through the write barrier. It took the ChunkedVec read lock for Gen2 and again for Gen1: four atomic read-modify-writes per lookup, two lookups per store. mark_card_for_ptr took the lock once per chunk scanned. The lock only guards concurrent growth, which Gen1 and Gen2 never see outside a collection: the collector is their only writer and runs with every mutator parked, and every barrier caller holds a heap permit. Gen0 still grows on any VM's TLAB refill and keeps the locked contains_ptr. Add ChunkedVec::locate_unsynchronized, an unsafe scan whose contract is that nothing changes the vector during the call, and route the heap's Gen1/Gen2 lookups through it.
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Warning Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption. Comment |
⏭️ Performance benchmarks were skippedPerf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to To run them on this PR, do any of the following, then push a commit (or re-run CI):
|
Binary size checks failed❌ 2 violations · ✅ 1 passed
Details & how to fixViolations:
Add/update baselines:
[artifacts.bridge_wasm]
file_bytes = 27362652
gzip_bytes = 8028539
[artifacts.packed-program]
file_bytes = 41364128
gzip_bytes = 16865485Generated by |
TLDR
Stacked on #5408 (wb-1). Every pointer store runs the write barrier, which calls
generation_ofon the container and on the value. Each call took theChunkedVecread lock for Gen2 and then again for Gen1: up to 8 atomic read-modify-writes per store.mark_card_for_ptrtook the lock once per chunk scanned. On aarch64 each of these is an outlined__aarch64_cas8_acq/ldadd8_relcall. In native richards they were ~31% of samples, plus 6.8% ingeneration_of.The lock only excludes concurrent growth, which Gen1 and Gen2 never see outside a collection.
How
ChunkedVec::locate_unsynchronized(ptr) -> Option<(chunk, offset)>: anunsafescan with no lock. Its contract is that nothing grows, clears, swaps or drops the vector during the call.contains_ptris now that scan under the read lock.ptr_in_chunked_vec,locate_in_chunked_vec, hencegeneration_ofandmark_card_for_ptr) use the unsynchronized scan.Why it's sound
evacuate_objectintogen2, the swap withinactive, andclear. It runs with every mutator parked and is single-threaded, so its owngeneration_ofcalls are sequential with its writes.HeapPtris only valid under one (heap_guard.rs), andsettle_ready/settle_errorrequire it.gen2.get()was read through theUnsafeCellwith no lock, andgeneration_of's comment states the invariant. This change only drops a lock that excluded growth that can't happen.Checks
bex_heaptests, with and withoutheap_debug, including wb-1's matrix. The membership unit test passes under Miri (tree borrows).rust_differential, debug assertions on, every mode.Spawns, on feat(compiler): compile BAML MIR to Rust #5264's base too; that is a separate fiber-annotation issue, not this change.Measurements are in wb-3; the two were benchmarked together.